﻿using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Web.DynamicData;

namespace MineSolutions.ExtendedQueryableFilter.Library
{
    public static class FilterQueryable
    {
        #region General

        public static IQueryable ApplyEqualityFilter<T>(IQueryable source, String propertyName, String value)
        {
            if (typeof(T) == typeof(DateTime))
            { 

            }
            return QueryableFilterUserControl.ApplyEqualityFilter(source, propertyName, value);
        }

        #endregion

        #region String

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T">Valid Types: String.</typeparam>
        /// <param name="source">The source System.Linq.IQueryable object.</param>
        /// <param name="propertyName">The name of the property.</param>
        /// <param name="value">The value for the propertyName parameter.</param>
        /// <returns>The transformed System.Linq.IQueryable object from the source System.Linq.IQueryable object.</returns>
        public static IQueryable ApplyContainsFilter<T>(IQueryable source, String propertyName, String value)
        {
            if (typeof(T) == typeof(String))
            {
                ParameterExpression parameterExpression;
                Expression propertyParameterExpression;
                PrepareParameterExpression(source, propertyName, out parameterExpression, out propertyParameterExpression);

                MethodInfo methodInfo = typeof(T).GetMethod("Contains");
                Expression binaryComparison = Expression.Call(propertyParameterExpression, methodInfo, new Expression[] { Expression.Constant(value, typeof(T)) });

                return BaseFilter(source, parameterExpression, propertyParameterExpression, binaryComparison);
            }

            return source;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T">Valid Types: String.</typeparam>
        /// <param name="source">The source System.Linq.IQueryable object.</param>
        /// <param name="propertyName">The name of the property.</param>
        /// <param name="value">The value for the propertyName parameter.</param>
        /// <returns>The transformed System.Linq.IQueryable object from the source System.Linq.IQueryable object.</returns>
        public static IQueryable ApplyStartWithFilter<T>(IQueryable source, String propertyName, String value)
        {
            if (typeof(T) == typeof(String))
            {
                ParameterExpression parameterExpression;
                Expression propertyParameterExpression;
                PrepareParameterExpression(source, propertyName, out parameterExpression, out propertyParameterExpression);

                MethodInfo methodInfo = typeof(T).GetMethod("StartsWith", new Type[] { typeof(String) });
                Expression binaryComparison = Expression.Call(propertyParameterExpression, methodInfo, new Expression[] { Expression.Constant(value, typeof(T)) });

                return BaseFilter(source, parameterExpression, propertyParameterExpression, binaryComparison);
            }

            return source;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T">Valid Types: String.</typeparam>
        /// <param name="source">The source System.Linq.IQueryable object.</param>
        /// <param name="propertyName">The name of the property.</param>
        /// <param name="value">The value for the propertyName parameter.</param>
        /// <returns>The transformed System.Linq.IQueryable object from the source System.Linq.IQueryable object.</returns>
        public static IQueryable ApplyEndsWithFilter<T>(IQueryable source, String propertyName, String value)
        {
            if (typeof(T) == typeof(String))
            {
                ParameterExpression parameterExpression;
                Expression propertyParameterExpression;
                PrepareParameterExpression(source, propertyName, out parameterExpression, out propertyParameterExpression);

                MethodInfo methodInfo = typeof(T).GetMethod("EndsWith", new Type[] { typeof(String) });
                Expression binaryComparison = Expression.Call(propertyParameterExpression, methodInfo, new Expression[] { Expression.Constant(value, typeof(T)) });

                return BaseFilter(source, parameterExpression, propertyParameterExpression, binaryComparison);
            }

            return source;
        }

        #endregion

        #region Numeric

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T">Valid Types: Int16, Int32, Int64, Decimal, Float, Double.</typeparam>
        /// <param name="source">The source System.Linq.IQueryable object.</param>
        /// <param name="propertyName">The name of the property.</param>
        /// <param name="value">The value for the propertyName parameter.</param>
        /// <returns>The transformed System.Linq.IQueryable object from the source System.Linq.IQueryable object.</returns>
        public static IQueryable ApplyGreaterThanFilter<T>(IQueryable source, String propertyName, String value)
        {
            Type selectedType;
            Boolean isValid;
            CheckType<T>(value, out selectedType, out isValid);

            if (selectedType != null && isValid)
            {
                ConstantExpression binaryComparison = Expression.Constant(value, selectedType);
                return GreaterThanFilter(source, propertyName, value, binaryComparison);
            }

            return source;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T">Valid Types: Int16, Int32, Int64, Decimal, Float, Double.</typeparam>
        /// <param name="source">The source System.Linq.IQueryable object.</param>
        /// <param name="propertyName">The name of the property.</param>
        /// <param name="value">The value for the propertyName parameter.</param>
        /// <returns>The transformed System.Linq.IQueryable object from the source System.Linq.IQueryable object.</returns>
        public static IQueryable ApplyGreaterThanOrEqualFilter<T>(IQueryable source, String propertyName, String value)
        {
            Type selectedType;
            Boolean isValid;
            CheckType<T>(value, out selectedType, out isValid);

            if (selectedType != null && isValid)
            {
                ConstantExpression binaryComparison = Expression.Constant(value, selectedType);
                return GreaterThanOrEqualFilter(source, propertyName, value, binaryComparison);
            }

            return source;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T">Valid Types: Int16, Int32, Int64, Decimal, Float, Double.</typeparam>
        /// <param name="source">The source System.Linq.IQueryable object.</param>
        /// <param name="propertyName">The name of the property.</param>
        /// <param name="value">The value for the propertyName parameter.</param>
        /// <returns>The transformed System.Linq.IQueryable object from the source System.Linq.IQueryable object.</returns>
        public static IQueryable ApplyLessThanFilter<T>(IQueryable source, String propertyName, String value)
        {
            Type selectedType;
            Boolean isValid;
            CheckType<T>(value, out selectedType, out isValid);

            if (selectedType != null && isValid)
            {
                ConstantExpression binaryComparison = Expression.Constant(value, selectedType);
                return LessThanFilter(source, propertyName, value, binaryComparison);
            }

            return source;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T">Valid Types: Int16, Int32, Int64, Decimal, Float, Double.</typeparam>
        /// <param name="source">The source System.Linq.IQueryable object.</param>
        /// <param name="propertyName">The name of the property.</param>
        /// <param name="value">The value for the propertyName parameter.</param>
        /// <returns>The transformed System.Linq.IQueryable object from the source System.Linq.IQueryable object.</returns>
        public static IQueryable ApplyLessThanOrEqualFilter<T>(IQueryable source, String propertyName, String value)
        {
            Type selectedType;
            Boolean isValid;
            CheckType<T>(value, out selectedType, out isValid);

            if (selectedType != null && isValid)
            {
                ConstantExpression binaryComparison = Expression.Constant(value, selectedType);
                return LessThanOrEqualFilter(source, propertyName, value, binaryComparison);
            }

            return source;
        }

        #endregion

        #region Date

        public static IQueryable ApplyDateEqualityFilter(IQueryable source, String propertyName, String value)
        {
            DateTime valueDateTime;
            if (DateTime.TryParse(value, out valueDateTime))
            {
                ParameterExpression parameterExpression;
                Expression propertyParameterExpression;
                PrepareParameterExpression(source, propertyName, out parameterExpression, out propertyParameterExpression);

                DateTime begindDay = new DateTime(valueDateTime.Year, valueDateTime.Month, valueDateTime.Day, 0, 0, 0, 0);
                ConstantExpression beginDayExpression = Expression.Constant(begindDay, typeof(DateTime));

                DateTime endDay = new DateTime(valueDateTime.Year, valueDateTime.Month, valueDateTime.Day, 23, 59, 59, 0);
                ConstantExpression endDayExpression = Expression.Constant(endDay, typeof(DateTime));

                BinaryExpression minimumComparison = Expression.GreaterThanOrEqual(propertyParameterExpression, beginDayExpression);
                BinaryExpression maximumComparison = Expression.LessThanOrEqual(propertyParameterExpression, endDayExpression);
                BinaryExpression binaryComparison = Expression.And(minimumComparison, maximumComparison);

                return BaseFilter(source, parameterExpression, propertyParameterExpression, binaryComparison);
            }

            return source;
        }

        public static IQueryable ApplyDateGreaterThanFilter(IQueryable source, String propertyName, String value)
        {
            DateTime valueDateTime;
            if (DateTime.TryParse(value, out valueDateTime))
            {
                DateTime begindDay = new DateTime(valueDateTime.Year, valueDateTime.Month, valueDateTime.Day, 0, 0, 0, 0);
                ConstantExpression binaryComparison = Expression.Constant(begindDay, typeof(DateTime));
                return GreaterThanFilter(source, propertyName, value, binaryComparison);
            }

            return source;
        }

        public static IQueryable ApplyDateGreaterThanOrEqualFilter(IQueryable source, String propertyName, String value)
        {
            DateTime valueDateTime;
            if (DateTime.TryParse(value, out valueDateTime))
            {
                DateTime begindDay = new DateTime(valueDateTime.Year, valueDateTime.Month, valueDateTime.Day, 0, 0, 0, 0);
                ConstantExpression binaryComparison = Expression.Constant(begindDay, typeof(DateTime));
                return GreaterThanOrEqualFilter(source, propertyName, value, binaryComparison);
            }

            return source;
        }

        public static IQueryable ApplyDateLessThanFilter(IQueryable source, String propertyName, String value)
        {
            DateTime valueDateTime;
            if (DateTime.TryParse(value, out valueDateTime))
            {
                DateTime endDay = new DateTime(valueDateTime.Year, valueDateTime.Month, valueDateTime.Day, 0, 0, 0, 0);
                ConstantExpression binaryComparison = Expression.Constant(endDay, typeof(DateTime));
                return LessThanFilter(source, propertyName, value, binaryComparison);
            }

            return source;
        }

        public static IQueryable ApplyDateLessThanOrEqualFilter(IQueryable source, String propertyName, String value)
        {
            DateTime valueDateTime;
            if (DateTime.TryParse(value, out valueDateTime))
            {
                DateTime endDay = new DateTime(valueDateTime.Year, valueDateTime.Month, valueDateTime.Day, 23, 59, 59, 0);
                ConstantExpression binaryComparison = Expression.Constant(endDay, typeof(DateTime));
                return LessThanOrEqualFilter(source, propertyName, value, binaryComparison);
            }

            return source;
        }

        #endregion

        #region DateTime

        public static IQueryable ApplyDateTimeGreaterThanOrEqualFilter(IQueryable source, String propertyName, String value)
        {
            DateTime valueDateTime;
            if (DateTime.TryParse(value, out valueDateTime))
            {
                ConstantExpression beginDayExpression = Expression.Constant(valueDateTime, typeof(DateTime));
                return GreaterThanOrEqualFilter(source, propertyName, value, beginDayExpression);
            }

            return source;
        }

        public static IQueryable ApplyDateTimeLessThanOrEqualFilter(IQueryable source, String propertyName, String value)
        {
            DateTime valueDateTime;
            if (DateTime.TryParse(value, out valueDateTime))
            {
                ConstantExpression endDayExpression = Expression.Constant(valueDateTime, typeof(DateTime));
                return LessThanOrEqualFilter(source, propertyName, value, endDayExpression);
            }

            return source;
        }

        #endregion

        #region Check Methods

        private static void CheckType<T>(String value, out Type selectedType, out Boolean isValid)
        {
            selectedType = null;
            isValid = false;

            if (typeof(T) == typeof(Int16))
            {
                CheckInt16(value, ref selectedType, ref isValid);
            }
            else if (typeof(T) == typeof(Int32))
            {
                CheckInt32(value, ref selectedType, ref isValid);
            }
            else if (typeof(T) == typeof(Int64))
            {
                CheckInt64(value, ref selectedType, ref isValid);
            }
            else if (typeof(T) == typeof(Decimal))
            {
                CheckDecimal(value, ref selectedType, ref isValid);
            }
            else if (typeof(T) == typeof(float))
            {
                CheckFloat(value, ref selectedType, ref isValid);
            }
            else if (typeof(T) == typeof(Double))
            {
                CheckDouble(value, ref selectedType, ref isValid);
            }
        }

        private static void CheckDouble(String value, ref Type selectedType, ref Boolean isValid)
        {
            selectedType = typeof(Double);
            Double valueDouble;
            isValid = Double.TryParse(value, out valueDouble);
        }

        private static void CheckFloat(String value, ref Type selectedType, ref Boolean isValid)
        {
            selectedType = typeof(float);
            float valueFloat;
            isValid = float.TryParse(value, out valueFloat);
        }

        private static void CheckDecimal(String value, ref Type selectedType, ref Boolean isValid)
        {
            selectedType = typeof(Decimal);
            Decimal valueDecimal;
            isValid = Decimal.TryParse(value, out valueDecimal);
        }

        private static void CheckInt64(String value, ref Type selectedType, ref Boolean isValid)
        {
            selectedType = typeof(Int64);
            Int64 valueInt64;
            isValid = Int64.TryParse(value, out valueInt64);
        }

        private static void CheckInt32(String value, ref Type selectedType, ref Boolean isValid)
        {
            selectedType = typeof(Int32);
            Int32 valueInt32;
            isValid = Int32.TryParse(value, out valueInt32);
        }

        private static void CheckInt16(String value, ref Type selectedType, ref Boolean isValid)
        {
            selectedType = typeof(Int16);
            Int16 valueInt16;
            isValid = Int16.TryParse(value, out valueInt16);
        }

        private static void CheckDate(String value, ref Type selectedType, ref Boolean isValid) 
        {
        }

        private static void CheckDateTime(String value, ref Type selectedType, ref Boolean isValid)
        {
        }

        #endregion

        #region Private Common

        private static void PrepareParameterExpression(IQueryable source, String propertyName, out ParameterExpression parameterExpression, out Expression propertyParameterExpression)
        {
            parameterExpression = Expression.Parameter(source.ElementType, propertyName);
            propertyParameterExpression = Expression.Property(parameterExpression, propertyName);
        }

        private static IQueryable BaseFilter(IQueryable source, ParameterExpression parameterExpression, Expression propertyParameterExpression, Expression binaryComparison)
        {
            LambdaExpression lambda = Expression.Lambda(binaryComparison, new ParameterExpression[] { parameterExpression });
            MethodCallExpression whereCall = Expression.Call(typeof(Queryable), "Where", new Type[] { source.ElementType }, source.Expression, Expression.Quote(lambda));
            return source.Provider.CreateQuery(whereCall);
        }

        private static IQueryable LessThanOrEqualFilter(IQueryable source, String propertyName, String value, ConstantExpression constantExpression)
        {
            ParameterExpression parameterExpression;
            Expression propertyParameterExpression;
            PrepareParameterExpression(source, propertyName, out parameterExpression, out propertyParameterExpression);

            BinaryExpression binaryComparison = Expression.LessThanOrEqual(propertyParameterExpression, constantExpression);

            return BaseFilter(source, parameterExpression, propertyParameterExpression, binaryComparison);
        }

        private static IQueryable LessThanFilter(IQueryable source, String propertyName, String value, ConstantExpression constantExpression)
        {
            ParameterExpression parameterExpression;
            Expression propertyParameterExpression;
            PrepareParameterExpression(source, propertyName, out parameterExpression, out propertyParameterExpression);

            BinaryExpression binaryComparison = Expression.LessThan(propertyParameterExpression, constantExpression);

            return BaseFilter(source, parameterExpression, propertyParameterExpression, binaryComparison);
        }

        private static IQueryable GreaterThanOrEqualFilter(IQueryable source, String propertyName, String value, ConstantExpression constantExpression)
        {
            ParameterExpression parameterExpression;
            Expression propertyParameterExpression;
            PrepareParameterExpression(source, propertyName, out parameterExpression, out propertyParameterExpression);

            BinaryExpression binaryComparison = Expression.GreaterThanOrEqual(propertyParameterExpression, constantExpression);

            return BaseFilter(source, parameterExpression, propertyParameterExpression, binaryComparison);
        }

        private static IQueryable GreaterThanFilter(IQueryable source, String propertyName, String value, ConstantExpression constantExpression)
        {
            ParameterExpression parameterExpression;
            Expression propertyParameterExpression;
            PrepareParameterExpression(source, propertyName, out parameterExpression, out propertyParameterExpression);

            BinaryExpression binaryComparison = Expression.GreaterThan(propertyParameterExpression, constantExpression);

            return BaseFilter(source, parameterExpression, propertyParameterExpression, binaryComparison);
        }

        #endregion
    }
}